package com.econ.powercloud.ems.timing.report;

import com.econ.powercloud.clickhouse.entity.EmsStatisticalMonth;
import com.econ.powercloud.clickhouse.entity.EmsStatisticalYear;
import com.econ.powercloud.ems.aop.TimingRetryValid;
import com.econ.powercloud.ems.common.enums.CalculationDataMatchTypeTagEnum;
import com.econ.powercloud.ems.common.enums.ChargeDischargeType;
import com.econ.powercloud.ems.common.enums.DitTypeEnum;
import com.econ.powercloud.ems.common.enums.ReportIdType;
import com.econ.powercloud.ems.common.util.DateTimeUtil;
import com.econ.powercloud.ems.common.util.KeepDecimalPointUtil;
import com.econ.powercloud.ems.constant.SysConstant;
import com.econ.powercloud.ems.dal.entity.EmsCalculationDataConfig;
import com.econ.powercloud.ems.dal.entity.EmsEnergyStation;
import com.econ.powercloud.ems.pojo.DitDataVO;
import com.econ.powercloud.ems.pojo.scene.OpenApiHisDataVO;
import com.econ.powercloud.ems.service.biz.ClickHouseBizService;
import com.econ.powercloud.ems.service.common.QueryHisDataCommonService;
import com.econ.powercloud.ems.service.dao.EmsCalculationDataConfigDAO;
import com.econ.powercloud.ems.service.dao.EmsEnergyStationDAO;
import com.econ.powercloud.ems.util.TimeUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author : zjf
 * @Description : 上网电量与下网电量等计算的定时任务计算
 * @Date : 2023/12/20 17:04
 * @Modified by : zjf
 */
@Component
@Slf4j
public class OnOffGridEnergyTask {
    public static final String ON_OFF_GRID_ENERGY_DAY = "on-off-gird-day";
    public static final String ON_OFF_GRID_ENERGY_MONTH = "on-off-gird-month";

    @Autowired
    private EmsCalculationDataConfigDAO emsCalculationDataConfigDAO;
    @Autowired
    private QueryHisDataCommonService queryHisDataCommonService;
    @Autowired
    private EmsEnergyStationDAO emsEnergyStationDAO;
    @Autowired
    private ClickHouseBizService clickHouseBizService;

    /**
     * 每天0点5分统计昨日的生产数据
     */
    @Scheduled(cron = "0 5 0 * * ?")
    @TimingRetryValid(lockName = ON_OFF_GRID_ENERGY_DAY)
    public void calculateProductDataDaily(){
        log.info("----------开始计算昨日生产数据----------");
        //获取昨日零点时间
        Long yesterdayStartTime = getYesterdayTimestamp();
        calculateProductDataDailyByTime(yesterdayStartTime);
        log.info("----------结束计算昨日生产数据----------");
    }

    /**
     * 每天1点10分统计昨日对应月份的上网与下网电量
     * (使用累计点号求差得出)
     */
    @Scheduled(cron = "0 10 0 * * ?")
    @TimingRetryValid(lockName = ON_OFF_GRID_ENERGY_MONTH)
    public void calculateProductDataMonth(){
        log.info("----------开始计算昨日对应的月份的生产数据----------");
        //获取昨日零点时间
        Long yesterdayStartTime = getYesterdayTimestamp();
        calculateProductDataMonthByTime(yesterdayStartTime);
        log.info("----------结束计算昨日对应的月份的生产数据----------");
    }

    /**
     * 计算某一天的上网与下网电量
     * @param startTime: 某一天的开始时间
     */
    public void calculateProductDataDailyByTime(Long startTime){
        //获取昨日结束时间
        Long endTime = TimeUtil.getDayEndTime(new Date(startTime));
        //获取生产运行数据的配置点号
        List<EmsCalculationDataConfig> configList = emsCalculationDataConfigDAO.getListByType(
                Lists.newArrayList(CalculationDataMatchTypeTagEnum.ON_GRID_ENERGY.getCode(), CalculationDataMatchTypeTagEnum.OFF_GRID_ENERGY.getCode(),
                        CalculationDataMatchTypeTagEnum.TOTAL_POWER.getCode(),CalculationDataMatchTypeTagEnum.DOD.getCode()));
        if(CollectionUtils.isEmpty(configList)){
            //未进行配置操作,不做处理
            log.warn("生产运行数据未进行配置操作,不做处理!");
            return;
        }
        //获取储能站信息
        List<EmsEnergyStation> stationList = emsEnergyStationDAO.list();
        if(CollectionUtils.isEmpty(stationList)){
            log.warn("未找到储能站,不在进行任何操作！");
            return;
        }
        //查询点号的昨日的历史数据,默认这些点号都是遥测点号
        List<DitDataVO> ditDataVOList = configList.stream().map(d-> DitDataVO.builder()
                .initDeviceCode(d.getInitDeviceCode())
                .dit(d.getDit()).ditType(d.getDitType())
                .build()).collect(Collectors.toList());
        List<OpenApiHisDataVO> dataVOList = queryHisDataCommonService.selectHisDataByDitDataList(startTime, endTime, ditDataVOList);
        //获取储能站信息
        EmsEnergyStation station = stationList.get(0);
        List<EmsStatisticalMonth> statisticalMonthList = Lists.newArrayList();
        //获取上下网的月报表数据
        List<EmsStatisticalMonth> onOffMonthData = this.calculateOnOffGridDailyByTime(station,configList, dataVOList,startTime);
        if(!CollectionUtils.isEmpty(onOffMonthData)){
            statisticalMonthList.addAll(onOffMonthData);
        }
        //获取充放电次数与充放电时长月报表数据
        List<EmsStatisticalMonth> powerMonthData = this.calculateChargeNumAndTimeDailyByTime(station,configList, dataVOList,startTime,endTime);
        if(!CollectionUtils.isEmpty(powerMonthData)){
            statisticalMonthList.addAll(powerMonthData);
        }
        //获取DOD月报表数据
        EmsStatisticalMonth dodData = this.calculateDODDailyByTime(station,configList,dataVOList,startTime,endTime);
        if(dodData != null){
            statisticalMonthList.add(dodData);
        }
        //插入昨日上下网电量数据
        clickHouseBizService.deleteMonthDataByTime(startTime, ReportIdType.ON_OFF_GRID_ENERGY.getCode());
        if (!CollectionUtils.isEmpty(statisticalMonthList)) {
            clickHouseBizService.emsStatisticalMonthListDataIntoClickHouse(statisticalMonthList);
        }
    }

    /**
     * 计算某月的生产数据
     * @param time: 某个月的时间
     */
    public void calculateProductDataMonthByTime(Long time){
        //获取储能站信息
        List<EmsEnergyStation> stationList = emsEnergyStationDAO.list();
        if(CollectionUtils.isEmpty(stationList)){
            log.warn("未找到储能站,不在进行任何操作!");
            return;
        }
        EmsEnergyStation station = stationList.get(0);
        //获取当前时间的当月的开始时间
        Long startMonthTime = DateTimeUtil.getStartOfMonth(new Date(time));
        //获取当前时间的当月的结束时间
        Long endMonthTime = DateTimeUtil.getEndOfMonth(new Date(time));
        List<EmsStatisticalMonth> monthList = clickHouseBizService.selectMonthListByCondition(startMonthTime, endMonthTime, ReportIdType.ON_OFF_GRID_ENERGY.getCode());
        //key:columnId
        Map<String, List<EmsStatisticalMonth>> columnIdMap = monthList.stream().collect(Collectors.groupingBy(d -> d.getColumnId()));
        //存放列的数据
        Map<String,Double> onOffEnergyMonthMap = Maps.newConcurrentMap();
        //开始计算昨日上下网电量
        columnIdMap.forEach((k,v)->{
            onOffEnergyMonthMap.put(k, 0.0);
            for (int i=1; i <= v.size(); i++){
                onOffEnergyMonthMap.put(k,(onOffEnergyMonthMap.get(k) + Double.parseDouble(v.get(i-1).getVal())));
            }
            //这里做一个额外的判断,DOD类型的做平均值
            if(Objects.equals(CalculationDataMatchTypeTagEnum.DOD.getCode(),k)){
                Double average = onOffEnergyMonthMap.get(k) / v.size();
                onOffEnergyMonthMap.put(k,average);
            }
        });
        List<EmsStatisticalYear> statisticalYearList = Lists.newArrayList();
        //向统计年表中插入数据
        onOffEnergyMonthMap.forEach((k,v)->{
            EmsStatisticalYear statisticalYear = EmsStatisticalYear.builder()
                    .energyStationId(station.getEnergyStationId())
                    .columnId(k).time(startMonthTime)
                    .gmtCreate(new Date())
                    .val(KeepDecimalPointUtil.keepTwoPoint(v))
                    .reportId(ReportIdType.ON_OFF_GRID_ENERGY.getCode())
                    .build();
            statisticalYearList.add(statisticalYear);
        });
        clickHouseBizService.deleteYearDataByTime(startMonthTime, ReportIdType.ON_OFF_GRID_ENERGY.getCode());
        if (!CollectionUtils.isEmpty(statisticalYearList)) {
            clickHouseBizService.emsStatisticalYearListDataIntoClickHouse(statisticalYearList);
        }
    }

    /**
     * 计算某一天的上下网电量
     * @param station: 储能站
     * @param configList: 配置信息
     * @param allHisDataList: 历史数据结果
     * @param startTime: 某一天的开始时间
     * @return
     */
    private List<EmsStatisticalMonth> calculateOnOffGridDailyByTime(EmsEnergyStation station,List<EmsCalculationDataConfig> configList,
                                                                   List<OpenApiHisDataVO> allHisDataList, Long startTime){
        //获取上下网电量的配置信息
        List<EmsCalculationDataConfig> onOffConfigList = configList.stream().filter(d-> Objects.equals(CalculationDataMatchTypeTagEnum.ON_GRID_ENERGY.getCode(),d.getMatchTypeTag()) ||
                Objects.equals(CalculationDataMatchTypeTagEnum.OFF_GRID_ENERGY.getCode(),d.getMatchTypeTag())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(onOffConfigList)){
            //未进行配置操作,不做处理
            log.warn("上网与下网电量未进行配置操作,不做处理!");
            return Collections.emptyList();
        }
        //获取配置信息的pid(点号)
        List<String> pidList = onOffConfigList.stream().map(d-> d.getInitDeviceCode() + "." + d.getDit()).collect(Collectors.toList());
        //过滤点号历史数据
        List<OpenApiHisDataVO> dataVOList = CollectionUtils.isEmpty(allHisDataList)?Collections.emptyList() : allHisDataList.stream().filter(d-> pidList.contains(d.getPid())).collect(Collectors.toList());
        Map<String,List<EmsCalculationDataConfig>> configMap = configList.stream().collect(Collectors.groupingBy(d->d.getMatchTypeTag()));
        //存放上下网的昨日电量Map,默认都是0
        Map<String,Double> onOffEnergyMap = Maps.newConcurrentMap();
        onOffEnergyMap.put(CalculationDataMatchTypeTagEnum.ON_GRID_ENERGY.getCode(),0.0);
        onOffEnergyMap.put(CalculationDataMatchTypeTagEnum.OFF_GRID_ENERGY.getCode(),0.0);
        if(!CollectionUtils.isEmpty(dataVOList)){
            //key：pid, value:历史数据,按照时间升序排列
            Map<String,List<OpenApiHisDataVO>> hisDataMap = dataVOList.stream().collect(Collectors.groupingBy(OpenApiHisDataVO::getPid,
                    Collectors.collectingAndThen(Collectors.toList(), list -> list.stream().sorted(Comparator.comparingLong(OpenApiHisDataVO::getTime)).collect(Collectors.toList()))));
            //开始计算昨日上下网电量
            configMap.forEach((k,v)->{
                v.stream().forEach(d-> {
                    String pid = d.getInitDeviceCode() + SysConstant.SPOT + d.getDit();
                    List<OpenApiHisDataVO> list = hisDataMap.get(pid);
                    if(!CollectionUtils.isEmpty(list)){
                        Double difference = Double.parseDouble(list.get(list.size()-1).getV()) - Double.parseDouble(list.get(0).getV());
                        onOffEnergyMap.put(k,(onOffEnergyMap.get(k) + difference));
                    }
                });
            });
        }
        List<EmsStatisticalMonth> statisticalMonthList = Lists.newArrayList();
        //向统计月表中插入数据
        onOffEnergyMap.forEach((k,v)->{
            EmsStatisticalMonth statisticalMonth = EmsStatisticalMonth.builder()
                    .energyStationId(station.getEnergyStationId())
                    .columnId(k).time(startTime)
                    .gmtCreate(new Date())
                    .val(KeepDecimalPointUtil.keepTwoPoint(v))
                    .reportId(ReportIdType.ON_OFF_GRID_ENERGY.getCode())
                    .build();
            statisticalMonthList.add(statisticalMonth);
        });
        return statisticalMonthList;
    }

    /**
     * 计算某一天充放电次数与充放电时间
     * @param station
     * @param configList
     * @param allHisDataList
     * @param startTime
     * @return
     */
    private List<EmsStatisticalMonth> calculateChargeNumAndTimeDailyByTime(EmsEnergyStation station,List<EmsCalculationDataConfig> configList,
                                                                          List<OpenApiHisDataVO> allHisDataList, Long startTime,Long endTime){
        //获取有功功率配置信息
        List<EmsCalculationDataConfig> onOffConfigList = configList.stream().filter(d-> Objects.equals(CalculationDataMatchTypeTagEnum.TOTAL_POWER.getCode(),d.getMatchTypeTag())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(onOffConfigList)){
            log.warn("有功功率点号信息未进行配置操作,不做处理!");
            return Collections.emptyList();
        }
        //获取配置信息的pid(点号)
        List<String> pidList = onOffConfigList.stream().map(d-> d.getInitDeviceCode() + "." + d.getDit()).collect(Collectors.toList());
        //过滤点号历史数据
        List<OpenApiHisDataVO> dataVOList = CollectionUtils.isEmpty(allHisDataList)?Collections.emptyList() : allHisDataList.stream().filter(d-> pidList.contains(d.getPid())).collect(Collectors.toList());
        //如果没有数据无需处理
        if(CollectionUtils.isEmpty(dataVOList)){
            return buildChargeNumAndTimeMonthData(station.getEnergyStationId(),0,0,0L,0L,startTime);
        }
        //历史数据按照时间升序
        dataVOList = dataVOList.stream().sorted(Comparator.comparing(OpenApiHisDataVO::getTime)).collect(Collectors.toList());
        //充放电次数map(key:ChargeDischargeType中的枚举code，value:次数)
        Map<Integer,Integer> chargeNumMap = Maps.newHashMap();
        chargeNumMap.put(ChargeDischargeType.CHARGE.getCode(),0);
        chargeNumMap.put(ChargeDischargeType.DISCHARGE.getCode(),0);
        //充放电时间map(key:ChargeDischargeType中的枚举code，value:时间,单位毫秒)
        Map<Integer,Long> chargeTimeMap = Maps.newHashMap();
        chargeTimeMap.put(ChargeDischargeType.CHARGE.getCode(),0L);
        chargeTimeMap.put(ChargeDischargeType.DISCHARGE.getCode(),0L);
        //每次充/放电的开始时间
        Long chargeOrDisChargeStartTime = dataVOList.get(0).getTime();
        //存放每个PCS的有功功率的当前断面数据,key；PCS的有功功率点号,value:对应PCS有功功率的断面值
        Map<String,Double> sectionMap = Maps.newHashMap();
        //最近一次充放电未发生改变的状态(主要用来和和循环时的状态进行比较),默认为待机状态
        Integer leastState = this.getChargeDisChargeType(Double.parseDouble(dataVOList.get(0).getV()), station.getMinChargePower(), station.getMinDischargePower());
        //循环历史数据进行处理
        for (OpenApiHisDataVO dataVO : dataVOList){
            //更新断面数据
            sectionMap.put(dataVO.getPid(),Double.parseDouble(dataVO.getV()));
            //获取当前断面数据的值的求和
            Double totalPower = sectionMap.values().stream().mapToDouble(d->d).sum();
            //获取充放电状态,充放电的阈值放在储能站表中
            Integer currentChargeType = this.getChargeDisChargeType(totalPower, station.getMinChargePower(), station.getMinDischargePower());
            //如果当前状态与最近一次的状态一致，无需处理
            if(Objects.equals(currentChargeType,leastState)){
                continue;
            }
            //如果最近一次的状态不为待机状态
            if(!Objects.equals(leastState,ChargeDischargeType.STANBY.getCode())){
                //充/放电次数+1
                chargeNumMap.put(leastState,chargeNumMap.get(leastState) + 1);
                //充/放电时间增加
                chargeTimeMap.put(leastState,chargeTimeMap.get(leastState) + dataVO.getTime() - chargeOrDisChargeStartTime);
            }
            chargeOrDisChargeStartTime = dataVO.getTime();
            leastState = currentChargeType;
        }
        //如果leastState状态不为待机状态，需要加上历史数据的最后时间到一天的结束时间的时间差值
        if(!Objects.equals(leastState,ChargeDischargeType.STANBY.getCode())){
            //充/放电次数+1
            chargeNumMap.put(leastState,chargeNumMap.get(leastState) + 1);
            //充/放电时间增加
            chargeTimeMap.put(leastState,chargeTimeMap.get(leastState) + endTime - chargeOrDisChargeStartTime);
        }
        return buildChargeNumAndTimeMonthData(station.getEnergyStationId(),chargeNumMap.get(ChargeDischargeType.CHARGE.getCode()),
                chargeNumMap.get(ChargeDischargeType.DISCHARGE.getCode()),chargeTimeMap.get(ChargeDischargeType.CHARGE.getCode()),
                chargeTimeMap.get(ChargeDischargeType.DISCHARGE.getCode()),startTime);
    }

    /**
     * 获取每日DOD
     * @param station
     * @param configList
     * @param allHisDataList
     * @param startTime
     * @param endTime
     * @return
     */
    private EmsStatisticalMonth calculateDODDailyByTime(EmsEnergyStation station,List<EmsCalculationDataConfig> configList,
                                                              List<OpenApiHisDataVO> allHisDataList, Long startTime,Long endTime){
        //这个DOD可以通过计算类型为DOD的计算点号来直接获取,获取有功功率配置信息
        List<EmsCalculationDataConfig> dodList = configList.stream().filter(d-> Objects.equals(CalculationDataMatchTypeTagEnum.DOD.getCode(),d.getMatchTypeTag())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(dodList)){
            log.warn("DOD点号信息未进行配置操作,不做处理!");
            return null;
        }
        //获取DOD的点号(这里认为只会有一个点号)
        String pid = dodList.get(0).getInitDeviceCode() + "." + dodList.get(0).getDit();
        //过滤点号历史数据
        List<OpenApiHisDataVO> dataVOList = CollectionUtils.isEmpty(allHisDataList)?Collections.emptyList() : allHisDataList.stream().filter(d-> Objects.equals(d.getPid(),pid)).collect(Collectors.toList());
        //如果没有数据无需处理
        if(CollectionUtils.isEmpty(dataVOList)){
           return this.buildStatisticalMonthData(station.getEnergyStationId(),CalculationDataMatchTypeTagEnum.DOD.getCode(),
                   startTime,"0",ReportIdType.ON_OFF_GRID_ENERGY.getCode());
        }
        String dodValue = dataVOList.stream().sorted(Comparator.comparing(OpenApiHisDataVO::getTime).reversed()).collect(Collectors.toList()).get(0).getV();
        return this.buildStatisticalMonthData(station.getEnergyStationId(),CalculationDataMatchTypeTagEnum.DOD.getCode(),
                startTime,dodValue,ReportIdType.ON_OFF_GRID_ENERGY.getCode());
    }

    /**
     * 根据有功功率之和获取对应的充放电状态
     * @param totalPower: 有功功率之和
     * @param minChargePower: 充电功率最小值
     * @param minDischargePower: 放电功率最小值
     * @return
     */
    private Integer getChargeDisChargeType(Double totalPower, Double minChargePower, Double minDischargePower){
        //判断是否为充电状态,首先判断两者的正负一致，在判断有功功率之和的绝对值大于等于充电功率最小值的绝对值
        if((totalPower * minChargePower) > 0 && (Math.abs(totalPower) - Math.abs(minChargePower))>= 0){
            return ChargeDischargeType.CHARGE.getCode();
        }
        //判断是否为放电状态,原理同上
        if((totalPower * minDischargePower) > 0 && (Math.abs(totalPower) - Math.abs(minDischargePower))>= 0){
            return ChargeDischargeType.DISCHARGE.getCode();
        }
        return ChargeDischargeType.STANBY.getCode();
    }

    /**
     * 获取昨天的零点时间戳
     * @return
     */
    public Long getYesterdayTimestamp(){
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, -1);
        Date date = calendar.getTime();
        Long startDayTime = TimeUtil.getDayStartTime(date);
        return startDayTime;
    }

    /**
     * 构造充放电次数与时间的月报表数据
     * @param stationId: 储能站id
     * @param chargeNum: 充电次数
     * @param disChargeNum: 放电次数
     * @param chargeTime: 充电时间(单位：毫秒)
     * @param disChargeTime: 放电时间(单位：毫秒)
     * @param time: 时间(时间戳)
     * @return
     */
    private List<EmsStatisticalMonth> buildChargeNumAndTimeMonthData(String stationId,Integer chargeNum,Integer disChargeNum,Long chargeTime,Long disChargeTime, Long time){
        List<EmsStatisticalMonth> statisticalMonthList = Lists.newArrayList();
        //充电次数
        statisticalMonthList.add(this.buildStatisticalMonthData(stationId,CalculationDataMatchTypeTagEnum.CHARGE_NUM.getCode(),time,
                chargeNum.toString(), ReportIdType.ON_OFF_GRID_ENERGY.getCode()));
        //放电次数
        statisticalMonthList.add(this.buildStatisticalMonthData(stationId,CalculationDataMatchTypeTagEnum.DISCHARGE_NUM.getCode(),time,
                disChargeNum.toString(), ReportIdType.ON_OFF_GRID_ENERGY.getCode()));
        //充电时间
        statisticalMonthList.add(this.buildStatisticalMonthData(stationId,CalculationDataMatchTypeTagEnum.CHARGE_TIME.getCode(),time,
                KeepDecimalPointUtil.keepTwoPoint((double) chargeTime/3600000), ReportIdType.ON_OFF_GRID_ENERGY.getCode()));
        //放电时间
        statisticalMonthList.add(this.buildStatisticalMonthData(stationId,CalculationDataMatchTypeTagEnum.DISCHARGE_TIME.getCode(),time,
                KeepDecimalPointUtil.keepTwoPoint((double) disChargeTime/3600000), ReportIdType.ON_OFF_GRID_ENERGY.getCode()));
        return statisticalMonthList;
    }

    private EmsStatisticalMonth buildStatisticalMonthData(String stationId,String columnId,Long time,String val,String reportId){
        return EmsStatisticalMonth.builder()
                .energyStationId(stationId).columnId(columnId).time(time)
                .gmtCreate(new Date()).val(val).reportId(reportId)
                .build();
    }
}
